package logica.locacao;

import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import logica.util.Data;
import logica.util.GeradorDeCodigo;

/**
 * Registros de locacao contem as informacoes acerca de uma locacao especifica: codigo exclusivo, o plano
 * que caracteriza este registro, as datas de entrega e devolucao, informacoes dos veiculos locados
 * e seus respectivos niveis de tanque, bem como o codigo do cliente que esta efetuando a locacao.
 * @author Equipe LocaLisa, Usa Quem Precisa
 */

public class RegistroDeLocacao implements Serializable{

	private transient static final long serialVersionUID = 1L;

	/**
	 * @author Equipe LocaLisa, Usa Quem Precisa
	 */
	
	public enum FormaDePagamento{
		DIARIAS, QUILOMETRAGEM;
	}
	
	private int codigoExclusivo;
	private Data entrega;
	private Data devolucao;
	private HashMap<Integer, HashSet<String>> infoVeiculos;
	private String codigoDoCliente;
	private PlanoDeLocacao plano;
	private double precoDaLocacao;
	private double multaTotal;
	private FormaDePagamento formaDePagamento;
	private int quilometragemDeclarada;
	
	/**
	 * O codigo exclusivo eh determinado com base nos registros ja existentes.
	 */
	public RegistroDeLocacao(){
		this.codigoExclusivo = GeradorDeCodigo.getCodigo();
		this.entrega = null;
		this.devolucao = null;
		this.infoVeiculos = null;
		this.codigoDoCliente = "";
		this.plano = null;
		this.precoDaLocacao = 0;
		this.multaTotal = 0;
		this.formaDePagamento = null;
		this.quilometragemDeclarada = 0;
	}

	/**
	 * Pega o codigo exclusivo deste registro de locacao.
	 * @return o codigo exclusivo
	 */
	public int getCodigoDoRegistro(){
		return this.codigoExclusivo;
	}

	/**
	 * Determina a quilometragem declarada pelo cliente para este registro de locacao.
	 * @param quilometragem a quilometragem declarada pelo cliente
	 * @throws Exception Excecao caso a quilometragem seja invalida
	 */
	public void setQuilometragemDeclarada(int quilometragem) throws Exception{
		if ( quilometragem <= 0 ){
			throw new Exception("\n \t <Quilometragem invalida!>");
		}
		this.quilometragemDeclarada = quilometragem;
	}
	
	/**
	 * Pega a quilometragem declarada.
	 * @return a quilometragem declarada pelo cliente
	 */
	public int getQuilometragemDeclarada(){
		return this.quilometragemDeclarada;
	}
	
	/**
	 * Muda a forma de pagamento do registro de locacao
	 * @param forma a forma de pagamento do registro de locacao
	 * @throws Exception Caso a forma de pagamento seja nula
	 */
	public void setFormaDePagamento(FormaDePagamento forma) throws Exception{
		if ( forma == null ){
			throw new Exception("\n \t <Forma de pagamento invalida!>");
		}
		this.formaDePagamento = forma;
	}
	
	/**
	 * Mostra a forma de pagamento do registro de locacao
	 * @return a forma de pagamento do registro de locacao
	 */
	public FormaDePagamento getFormaDePagamento(){
		return this.formaDePagamento;
	}
	
	/**
	 * Registra o valor do preco desta locacao.
	 * @param precoDaLocacao um valor inteiro correspondente ao preco desta locacao
	 * @throws Exception Excecao se o preco for menor que 0
	 */
	public void setPrecoDaLocacao(double precoDaLocacao) throws Exception{
		if ( precoDaLocacao < 0 ){
			throw new Exception("\n \t <Preco da locacao invalido!>");
		}
		this.precoDaLocacao = precoDaLocacao;
	}
	
	/**
	 * Registra o valor total da multa que o cliente pagou na devolucao.
	 * @param valorTotalDaMulta um valor inteiro que corresponde a multa paga pelo cliente
	 * @throws Exception Excecao caso o valor para a multa seja menor que 0
	 */
	public void setMultaTotal(double valorTotalDaMulta) throws Exception{
		if ( valorTotalDaMulta < 0 ){
			throw new Exception("\n \t <Valor total da multa paga invalida!>");
		}
		this.multaTotal = valorTotalDaMulta;
	}
	
	/**
	 * Retorna um valor inteiro correspondente ao preco da locacao pago pelo cliente.
	 * @return o preco da locacao
	 */
	public double getPrecoDaLocacao(){
		return this.precoDaLocacao;
	}
	
	/**
	 * Retorna um valor inteiro correspondente a multa paga pelo cliente na hora da devolucao.
	 * @return o valor total da multa
	 */
	public double getMultaTotal(){
		return this.multaTotal;
	}
	
	/**
	 * Determina um plano de locacao que caracteriza este registro de locacao.
	 * @param plano O plano de locacao que caracteriza este registro de locacao
	 * @throws Exception Excecao caso o plano de locacao passado para o metodo seja nulo
	 */
	public void setPlanoDeLocacao(PlanoDeLocacao plano) throws Exception{
		if ( plano == null ){
			throw new Exception("\n \t <Plano de locacao invalido!>");
		}
		this.plano = plano;
	}
	
	/**
	 * Retorna o plano de locacao que caracteriza este registro de locacao.
	 * @return o plano de locacao que caracteriza este registro
	 */
	public PlanoDeLocacao getPlanoDeLocacao(){
		return this.plano;
	}
	
	/**
	 * Determina uma data de entrega, ou seja, a data que o cliente recebeu os veiculos.
	 * @param entrega A data que o cliente recebeu os veiculos.
	 * @throws Exception Excecao caso a data passada como parametro seja nula, ou caso essa data
	 * seja mais antiga que a data de devolucao, caso a mesma ja exista.
	 */
	public void setDataEntrega(Data entrega) throws Exception{
		if ( entrega == null ){
			throw new Exception("\n \t <Data de locacao invalida!>");
		}
		boolean validado = false;
		if ( devolucao != null ){

			if ( Data.getDiferencaDeDiasEntreDatas(entrega, devolucao) >= 0 ){
				validado = true;
			}
			
		}
		else{
			validado = true;
		}
		if ( !validado ){
			throw new Exception("\n \t <Data de locacao nao pode ser mais recente que a data de devolucao assumida como compromisso!>");
		}
		this.entrega = entrega;
	}
	
	/**
	 * Determina uma data de devolucao, ou seja, a data que o cliente devolveu os veiculos.
	 * @param devolucao A data que o cliente devolveu os veiculos.
	 * @throws Exception Excecao caso a data passada como parametro seja nula, ou caso essa data
	 * seja mais recente que a data de entrega, caso a mesma ja exista.
	 */
	public void setDataDevolucao(Data devolucao) throws Exception{
		if ( devolucao == null ){
			throw new Exception("\n \t <Data de devolucao invalida!>");
		}
		boolean validado = false;
		if ( entrega != null ){

			if ( Data.getDiferencaDeDiasEntreDatas(entrega, devolucao) >= 0 ){
				validado = true;
			}
			
		}
		else{
			validado = true;
		}
		if ( !validado ){
			throw new Exception("\n \t <Data de devolucao assumida como compromisso nao pode ser mais antiga que a data de locacao!>");
		}
		this.devolucao = devolucao;
	}
	
	/**
	 * Determina as informacoes dos veiculos, que sao estruturadas com um mapa cujas chaves sao inteiros
	 * referentes a diferentes niveis de tanque, e cada chave redireciona para um conjunto de codigos de
	 * veiculos, que sao os veiculos que possuem esse determinado nivel de tanque.
	 * @param infoVeiculos O mapa de codigos de veiculos categorizados por niveis de tanque
	 * @throws Exception Excecao caso o mapa seja nulo ou caso alguma chave de nivel de tanque seja um
	 * inteiro negativo, bem como se algum codigo de veiculo (RENAVAM) for de formato invalido (nao vazio,
	 * nao nulo e so formado por digitos)
	 */
	public void setInfoVeiculos(HashMap<Integer, HashSet<String>> infoVeiculos)
		throws Exception{
		
		if ( infoVeiculos == null ){
			throw new Exception("\n \t <Informacoes dos veiculos invalidas!>");
		}
		
		for ( int nivel : infoVeiculos.keySet() ){
			if ( nivel < 0 || nivel > 100 ){
				throw new Exception("\n \t <Nivel de tanque invalido!>");
			}
			for ( String codigoDoVeiculo : infoVeiculos.get(nivel) ){
				if ( codigoDoVeiculo.trim().isEmpty() ){
					throw new Exception("\n \t <Codigo de veiculo invalido!>");
				}
				for ( int i=0; i<codigoDoVeiculo.length(); i++  ){
					if ( !Character.isDigit(codigoDoVeiculo.charAt(i)) ){
						throw new Exception("\n \t <Codigo do veiculo invalido!>");
					}
				}
			}
		}
		
		this.infoVeiculos = infoVeiculos;
	}
	
	/**
	 * Determina o codigo do cliente (CPF/CNPJ) que esta efetuando essa locacao.
	 * @param codigoDoCliente O codigo do cliente (CPF/CNPJ) que esta efetuando a locacao
	 * @throws Exception Excecao caso esse codigo seja nulo
	 */
	public void setCodigoDoCliente(String codigoDoCliente) throws Exception{
		if ( codigoDoCliente == null || codigoDoCliente.trim().isEmpty() ){
			throw new Exception("\n \t <Codigo do cliente invalido!>");
		}
		this.codigoDoCliente = codigoDoCliente;
	}
	
	/**
	 * Retorna a data de entrega dos veiculos para o cliente.
	 * @return a data de entrega
	 */
	public Data getDataEntrega(){
		return this.entrega;
	}
	
	/**
	 * Retorna a data de devolucao dos veiculos pelo cliente.
	 * @return a data de devolucao
	 */
	public Data getDataDevolucao(){
		return this.devolucao;
	}
	
	/**
	 * Retorna um mapa com as informacoes dos codigos de veiculos categorizados pelo nivel de tanque dos
	 * veiculos com esses codigos.
	 * @return um mapa com as informacoes acerca dos veiculos locados
	 */
	public HashMap<Integer, HashSet<String>> getInfoVeiculos(){
		return this.infoVeiculos;
	}
	
	/**
	 * Retorna o conjunto de veiculos que foram locados pelo cliente, sem considerar seus niveis de tanque.
	 * @return o conjunto de veiculos que foram locados pelo cliente
	 */
	public HashSet<String> getCodigosDeVeiculosLocados(){
		if ( this.infoVeiculos == null ){
			return null;
		}
		Collection<HashSet<String>> colecaoDeConjuntosDeCodigos =  this.infoVeiculos.values();
		HashSet<String> retorno = new HashSet<String>();
		
		for ( HashSet<String> conjunto : colecaoDeConjuntosDeCodigos ){
			for ( String codigoDeVeiculo : conjunto ){
				retorno.add(codigoDeVeiculo);
			}
		}	
		return retorno;
	}
	
	/**
	 * Remove o codigo de veiculo locado especificado.
	 * @param codigoASerRemovido o codigo de veiculo especificado
	 */
	
	public void removeCodigoDeVeiculoLocado(String codigoASerRemovido){
		for ( Integer nivelDeTanque : this.infoVeiculos.keySet() ){
			for ( String codigo : this.infoVeiculos.get(nivelDeTanque) ){
				if ( codigo.equals(codigoASerRemovido) ){
					this.infoVeiculos.get(nivelDeTanque).remove(codigoASerRemovido);
				}
			}
		}
	}
	
	/**
	 * Retorna o codigo do cliente (CPF/CNPJ)  que efetuou esta locacao.
	 * @return uma string que eh o codigo do cliente (CPF/CNPJ)
	 */
	public String getCodigoDoCliente(){
		return this.codigoDoCliente;
	}
	
	/**
	 * Mostra uma representacao do objeto em String, revelando alguns
	 * de seus atributos
	 * @return Uma representacao em String do objeto RegistroDeLocacao
	 */
	@Override
	public String toString(){
		String format = "";
		format += " \t ______________________________________________________________\n";
		format += "\n \t Registro de Locacao "+this.getCodigoDoRegistro()+ " do cliente de CPF "+this.getCodigoDoCliente();
		format += "\n \t Forma de pagamento: "+this.getFormaDePagamento();
		format += "  Plano de locacao: "+this.getPlanoDeLocacao().getCodigo();
		@SuppressWarnings("unused")
		Iterator<String> iterador;
		for ( Integer nivelDeTanque : this.getInfoVeiculos().keySet() ){
			format += "\n \t RENAVAMS de veiculos com nivel de tanque ("+nivelDeTanque+"%):\n \t   ";
			for ( String codigo : this.getInfoVeiculos().get(nivelDeTanque) ){
				format += "  "+codigo;
			}
		}
		format += "\n \t Data da locacao: "+this.getDataEntrega().getDataPorExtenso();
		if ( this.getDataDevolucao() != null ){
			format += "\n \t Compromisso de devolucao em: "+this.getDataDevolucao().getDataPorExtenso();
		}
		else{
			format += "\n \t Data de compromisso de devolucao nao especificada";
		}
		format += String.format("\n \t Preco da locacao: R$ %.2f", this.getPrecoDaLocacao());
		if ( this.getMultaTotal() != 0 ){
			format += String.format("\n \t Multas na devolucao: R$ %.2f", this.getMultaTotal());
		}
		if ( this.getQuilometragemDeclarada() != 0 ){
			format += "\n \t Quilometragem declarada: "+this.getQuilometragemDeclarada()+" km";
		}
		format += "\n \t ______________________________________________________________\n";
		return format;
	}
	
}